package pl.edu.mimuw.irs.core.model.design;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.annotation.XmlAnyElement;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.analysis.subnet.AtomicSubnet;
import pl.edu.mimuw.irs.core.model.design.Fragmentation.Type;
import pl.edu.mimuw.irs.core.model.design.exception.InternalIRSException;
import pl.edu.mimuw.irs.core.model.design.exception.InternalIRSException.Cause;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Net;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Converter;
import pl.edu.mimuw.irs.core.util.Position;

/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 */
public class IRSTransition extends Transition implements IRSNodeInterface, Serializable {
	
	private static final long serialVersionUID = 1072788871171239189L;
	
	// *** Attributes
	
	private NodeType nodeType;
	private Fragmentation fragmentation;
	private IRSNodeInterface parent;
	// Children should be in specific order - intuitive according to fragmentations.
	private List<IRSNodeInterface> children;
	// Node supports
	private final static Set<Fragmentation.Type> supportedFragmentations;
	static {
		supportedFragmentations = new LinkedHashSet<Type>();
		supportedFragmentations.add(Type.SEQUENCE);
		supportedFragmentations.add(Type.ALTERNATIVE);
		supportedFragmentations.add(Type.ACTIVITY);
		supportedFragmentations.add(Type.NONE);
	}
	private boolean expanded = false;
	
	// Getters and setters
	public NodeType getNodeType() {
		return nodeType;
	}
	public void setNodeType(NodeType nodeType) {
		this.nodeType = nodeType;
	}
	public Fragmentation getFragmentation() {
		if (fragmentation == null) fragmentation = new Fragmentation();
		return fragmentation;
	}
	public void setFragmentation(Fragmentation fragmentation) {
		if (fragmentation == null) fragmentation = new Fragmentation(Type.NONE);
		if (!getSupportedFragmentations().contains(fragmentation.getType())) 
			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE,
					fragmentation);
		handleFragmentation(fragmentation);
	}
	@XmlAnyElement
	public IRSNodeInterface getParent() {
		return parent;
	}
	public void setParent(IRSNodeInterface parent) {
		this.parent = parent;
	}
	/**
	 * List returned by this method should NOT be modified.
	 */
	public List<IRSNodeInterface> getChildren() {
		if (children == null) children = new ArrayList<IRSNodeInterface>();
		return children;
	}
	public void setChildren(List<IRSNodeInterface> children, Fragmentation fragmentation) {
		removeChildren();
		for (IRSNodeInterface child : children) {
			addChild(child);
		}
		this.fragmentation = fragmentation;
	}
	public void addChild(int index, IRSNodeInterface node) {
		this.getChildren().add(index, node);
		node.setParent(this);
	}
	public void addChild(IRSNodeInterface node) {
		this.getChildren().add(node);
		node.setParent(this);
	}
	public void removeChild(IRSNodeInterface node) {
		this.getChildren().remove(node);
		node.setParent(null);
	}
	public boolean isRoot() {
		// Transition cannot be root
		return false;
	}
	public boolean isAtomic() {
		return this.getFragmentation().getType().equals(Type.NONE);
	}
	public Set<Type> getSupportedFragmentations() {
		return supportedFragmentations;
	}
	public boolean isExpanded() {
		return expanded && !getFragmentation().getType().equals(Type.NONE);
	}
	public void setExpanded(boolean expand) {

		List<IRSNodeInterface> children = getChildren();
		
		if (children.size() > 0) {
			List<Position> positions = new LinkedList<Position>();
			for (IRSNodeInterface node : getChildren()) {
				positions.add(node.getPosition());
			}
			Position[] positionsArray = new Position[positions.size()];
			Position avgPosition = Position.getAverage(positions.toArray(positionsArray));
			
			if (expand) {
				Position diff = this.getPosition().sub(avgPosition);
				for (IRSNodeInterface node : children) {
					node.setPosition(node.getPosition().sum(diff));
				}
				this.expanded = expand;
			} else {
				this.setPosition(avgPosition);
				this.expanded = expand;
			}
		} else {
			if (expand) {
				this.expanded = expand;
			} else {
				this.expanded = expand;
			}
		}
		
	}
//	@Override
//	public void addArc(Arc arc) {
//		IRSArc irsArc = Converter.asIRSArc(arc);
//		IRSNodeInterface irsSource = Converter.asIRSNodeInterface(irsArc.getSource());
//		IRSNodeInterface irsTarget = Converter.asIRSNodeInterface(irsArc.getTarget());
//		
//		super.addArc(irsArc);
//		
//		for (IRSNodeInterface child : getOutputChildren()) {
//			if (irsSource == this) irsArc.copy(child, irsTarget);
//		}
//		for (IRSNodeInterface child : getInputChildren()) {
//			if (irsTarget == this) irsArc.copy(irsSource, child);
//		}
//	}
	
	// *** Constructors
	
	public IRSTransition(IDManager idManager, String id, NodeType nodeType, Fragmentation fragmentation, Position position) {
		super(idManager, id, position);
		setNodeType(nodeType);
		setFragmentation(fragmentation);
	}
	public IRSTransition(IDManager idManager, String id, NodeType nodeType, Position position) {
		this(idManager, id, nodeType, null, position);
	}
	public IRSTransition(IDManager idManager, String id, Position position) {
		this(idManager, id, NodeType.TRANSITION, position);
	}
	public IRSTransition(IDManager idManager, Position position) {
		this(idManager, null, position);
	}
	public IRSTransition(IDManager idManager, String id) {
		this(idManager, id, new Position());
	}
	public IRSTransition(IDManager idManager) {
		this(idManager, (String)null);
	}
	
	// *** Methods
	
	// Invoked on fragmentation change - causes node structural change.
	private void handleFragmentation(Fragmentation fragmentation) {
		// TODO : poprawić, żeby sekwencje były robione zależnie od kontekstu
		switch (fragmentation.getType()) {
			case NONE:
				removeChildren();
				break;
			case SEQUENCE:
				setSequence(fragmentation);
				break;
			case ALTERNATIVE:
				setAlternative(fragmentation);
				break;
			case ACTIVITY:
				setActivity(fragmentation);
				break;
			default:
				throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE,
						fragmentation);
		}
	}
	
	private void setSequence(Fragmentation fragmentation) {
		Fragmentation.Type parentFragmentation;
		if (isRoot()) parentFragmentation = Type.NONE;
		else parentFragmentation = getParent().getFragmentation().getType();
		
		if (Type.SEQUENCE.equals(parentFragmentation)) {
			int index = getParent().getChildren().indexOf(this);
			for (IRSNodeInterface node : createSequence(fragmentation)) {
				getParent().addChild(index++, node);
			}
			this.fragmentation = new Fragmentation(Type.NONE);
			this.destroy();
		} else {
			// TODO : inteligentne obsłużenie różnych przypadków fragmentacji elementu
			//removeChildren();
			setChildren(createSequence(fragmentation), fragmentation);
		}
	}
	
	private List<IRSNodeInterface> createSequence(Fragmentation fragmentation) {
		IDManager idManager = getIdManager();
		List<IRSNodeInterface> sequence = new LinkedList<IRSNodeInterface>();
		Net net = getNet();
		
		if (fragmentation.getModifier() > 0) {
			IRSTransition firstTransition = Converter.asIRSTransition(copy());
			cloneIncomingArcsTo(firstTransition);
			IRSTransition lastTransition = Converter.asIRSTransition(copy());
			cloneOutgoingArcsTo(lastTransition);
			IRSPlace lastPlace = new IRSPlace(idManager, this.getPosition());
			net.addPlace(lastPlace);
			sequence.add(firstTransition);
			sequence.add(lastPlace);
			net.addArc(new IRSArc(idManager, firstTransition, lastPlace));
			for (int i = 1; i < fragmentation.getModifier(); i++) {
				IRSTransition newTransition = new IRSTransition(idManager, getPosition());
				net.addTransition(newTransition);
				IRSPlace newPlace = new IRSPlace(idManager, getPosition());
				net.addPlace(newPlace);
				sequence.add(newTransition);
				sequence.add(newPlace);
				net.addArc(new IRSArc(idManager, lastPlace, newTransition));
				net.addArc(new IRSArc(idManager, newTransition, newPlace));
				lastPlace = newPlace;
			}
			sequence.add(lastTransition);
			net.addArc(new IRSArc(idManager, lastPlace, lastTransition));
		}
		
		return sequence;
	}

	private void setAlternative(Fragmentation fragmentation) {
		List<IRSNodeInterface> nodes = new ArrayList<IRSNodeInterface>();
		for (int i = 0; i < 1 + fragmentation.getModifier(); i++) {
			IRSTransition newTransition = Converter.asIRSTransition(copy());
			cloneIncomingArcsTo(newTransition);
			cloneOutgoingArcsTo(newTransition);
			nodes.add(newTransition);
		}
		setChildren(nodes, fragmentation);
	}

	private void setActivity(Fragmentation fragmentation) {
		Net net = getNet();
		
		if (fragmentation.getModifier() > 0) {
			IRSRequirement newPlace = new IRSRequirement(getIdManager(), getPosition());
			IRSTransition newTransition = copyAsActivity(newPlace);
			removeChildren();
			cloneIncomingArcsTo(newTransition);
			cloneOutgoingArcsTo(newTransition);
			net.addPlace(newPlace);
			net.addArc(new IRSArc(getIdManager(), newTransition, newPlace));
			net.addArc(new IRSArc(getIdManager(), newPlace, newTransition));
			addChild(newTransition);
			addChild(newPlace);
			if (fragmentation.getModifier() > 1) {
				newPlace.setFragmentation(new Fragmentation(Type.CONCURRENCY, 
						fragmentation.getModifier()));
			}
		}
		this.fragmentation = fragmentation;
	}

	public List<IRSNodeInterface> getAtomicNodes() {
		List<IRSNodeInterface> leafs = new LinkedList<IRSNodeInterface>();
		if (getFragmentation().getType().equals(Type.NONE)) {
			leafs.add(this);
		} else {
			for (IRSNodeInterface node : getChildren()) {
				leafs.addAll(node.getAtomicNodes());
			}
		}
		return leafs;
	}

	public List<IRSNodeInterface> getExpandedNodes() {
		List<IRSNodeInterface> leafs = new LinkedList<IRSNodeInterface>();
		if (!isExpanded()) {
			leafs.add(this);
			return leafs;
		} else {
			for (IRSNodeInterface node : getChildren()) {
				IRSNodeInterface rcpNode = (IRSNodeInterface)node;
				leafs.addAll(rcpNode.getExpandedNodes());
			}
		}
		return leafs;
	}
	
	public AtomicSubnet getAtomicWorkflow(AtomicSubnet atomicSubnet) {
		if (Type.NONE.equals(getFragmentation().getType())) {
			// XXX : wiecej szczegółów miejsca
			Transition transition = new Transition(atomicSubnet.getIdManager(),
					atomicSubnet.getIdManager().getPrefixedNewId(getId()), getPosition());
			atomicSubnet.addTransition(transition);
			atomicSubnet.getTransitionsMap().put(this, transition);
			Map<IRSPlace, Place> placesMap = atomicSubnet.getPlacesMap();
			for (IRSArc arc : getAtomicIncomingArcs()) {
				Place place = placesMap.get(Converter.asIRSPlace(arc.getPlace()));
				if (place != null) {
					atomicSubnet.addArc(place, transition, arc.getWeight());
				}
			}
			for (IRSArc arc : getAtomicOutgoingArcs()) {
				Place place = placesMap.get(Converter.asIRSPlace(arc.getPlace()));
				if (place != null) {
					atomicSubnet.addArc(transition, place, arc.getWeight());
				}
			}
		} else {
			for (IRSNodeInterface node : getChildren()) {
				atomicSubnet = node.getAtomicWorkflow(atomicSubnet);
			}
		}
		return atomicSubnet;
	}
	
	@Override
	public List<IRSArc> getAtomicIncomingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getIncomingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSPlace place = Converter.asIRSPlace(irsArc.getPlace());
			if (place.isAtomic()) atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}
	
	@Override
	public List<IRSArc> getAtomicOutgoingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getOutgoingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSPlace place = Converter.asIRSPlace(irsArc.getPlace());
			if (place.isAtomic()) atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}
	
	public IRSNodeInterface copy() {
		IRSTransition clone = new IRSTransition(getIdManager(),
				getIdManager().getPrefixedNewId(getId()), getNodeType(), getPosition().copy());
		copyContentsTo(clone);
		if (getNet() != null) getNet().addTransition(clone);
		return clone;
	}
	
	public IRSActivity copyAsActivity(IRSRequirement requirement) {
		IRSActivity clone = new IRSActivity(getIdManager(),
				getIdManager().getPrefixedNewId(getId()), getNodeType(), getPosition().copy(), requirement);
		copyContentsTo(clone);
		if (getNet() != null) getNet().addTransition(clone);
		return clone;
	}
	
	public IRSNodeInterface copyContentsTo(IRSNodeInterface node) {
		List<IRSNodeInterface> children = getChildren();
		List<IRSNodeInterface> clonedChildren = new LinkedList<IRSNodeInterface>();
		for (IRSNodeInterface child : children) {
			clonedChildren.add(child.copy());
		}
		
		for (int i = 0; i < children.size(); i++) {
			for (Arc arc : children.get(i).getIncomingArcs()) {
				int pos = children.indexOf(arc.getSource());
				if (pos >= 0) 
					((IRSArc)arc).copy(clonedChildren.get(pos), clonedChildren.get(i));
			}
			for (Arc arc : children.get(i).getOutgoingArcs()) {
				int pos = children.indexOf(arc.getTarget());
				if (pos >= 0) 
					((IRSArc)arc).copy(clonedChildren.get(i), clonedChildren.get(pos));
			}
		}
		
		node.setChildren(clonedChildren, getFragmentation().copy());
		
		return node;
	}
	
	protected void cloneIncomingArcsTo(IRSTransition transition) {
		for (Arc arc : getIncomingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			irsArc.copy(Converter.asIRSPlace(irsArc.getPlace()), transition);
		}
	}

	protected void cloneOutgoingArcsTo(IRSTransition transition) {
		for (Arc arc : getOutgoingArcs()) {
			IRSArc irsArc = (IRSArc) arc;
			irsArc.copy(transition, Converter.asIRSPlace(irsArc.getPlace()));
		}
	}

	public void destroy() {
		IRSNodeInterface parent = getParent();
		setParent(null);
		super.destroy();
		if (parent != null) parent.removeChild(this);
		removeChildren();
	}

//	@Override
//	public List<IRSNodeInterface> getInputChildren() {
//		switch (getFragmentation().getType()) {
//		case NONE:
//			return new LinkedList<IRSNodeInterface>();
//		case SEQUENCE:
//			return Collections.singletonList(getChildren().get(0));
//		case ALTERNATIVE:
//			LinkedList<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
//			for (IRSNodeInterface child : getChildren()) {
//				list.add(child);
//			}
//			return list;
//		case ACTIVITY:
//			return Collections.singletonList(getChildren().get(0));
//		default:
//			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE,
//					getFragmentation());
//		}
//	}
//	
//	@Override
//	public List<IRSNodeInterface> getOutputChildren() {
//		switch (getFragmentation().getType()) {
//		case NONE:
//			return new LinkedList<IRSNodeInterface>();
//		case SEQUENCE:
//			return Collections.singletonList(getChildren().get(getChildren().size() - 1));
//		case ALTERNATIVE:
//			LinkedList<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
//			for (IRSNodeInterface child : getChildren()) {
//				list.add(child);
//			}
//			return list;
//		case ACTIVITY:
//			return Collections.singletonList(getChildren().get(0));
//		default:
//			throw new InternalIRSException(Cause.UNSUPPORTED_FRAGMENTATION_TYPE,
//					getFragmentation());
//		}
//	}
	
	private void removeChildren() {
		List<IRSNodeInterface> list = new LinkedList<IRSNodeInterface>();
		for (IRSNodeInterface node : getChildren()) {
			if (node != this) list.add(node);
		}
		for (IRSNodeInterface node : list) {
			node.destroy();
		}
		this.fragmentation = new Fragmentation(Type.NONE);
	}
	
	@Override
	public boolean isAncestorOf(IRSNodeInterface node) {
		// TODO : wydajność do poprawienia!
		IRSNodeInterface parent = node.getParent();
		return (parent != null) && (parent == this || isAncestorOf(parent));
	}
}
